/*
    backend for TimeTagger, an OpalKelly based single photon counting library
    Copyright (C) 2011  Markus Wick <wickmarkus@web.de>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef TIMETAGGER_H
#define TIMETAGGER_H

#include "Thread.h"
#include "Logger.h"

#include <string>
#include "okFrontPanelDLL.h"

const std::string bitfilename = "TimeTaggerController.bit";		// location of bitfile for fpga
const int bytes_per_word = 4;									

const int workers = 4; 											// count of threads
const int channels = 8*2;										// musst be the same as on the FPGA
																// *2 ist for up/down
const int distribution = 1<<7;									// 7 Bit für Zeitauflösung
const int picosecounds = 6000;									// time delay for each macro cycle

const int blocksize = 1024;										// size of data, transmitted at once [byte]
const int ibuffersize = blocksize * 256;						// data to read and convert at once [byte]
const int obuffersize = ibuffersize / bytes_per_word;			// buffer size for output, do not change [tag]


class _Iterator;
class _Tagger;
class _Worker;
class _Iter;
class Control;

struct Tag {
	bool overflow;
	int chan;
	long long time;
};

class _Tagger {
	friend class Control;
public:
	static _Tagger* getTagger(_Iterator *it);

	_Iter* addIterator(_Iterator *it);
	
	void registerChannel(int chan);
	void unregisterChannel(int chan);
	
	static void getDistributionCount(int c, double **ARGOUTVIEWM_ARRAY1, int *DIM1);
	static void setSerial(std::string s);
	
	void iter(_Worker *w);

	void warning(int lvl, const char* msg);

private:
	
	_Worker* worker[workers];
	
	_Tagger();
	~_Tagger();

	void configureFpga();
	void configureChannel();

	void start();
	void stop();

	void read(_Worker *w);
	void convert(_Worker *w);

	void update_distribution();

	// Tagger Loader
	static _Tagger* tagger;
	static std::string serial;
	static current_mutex tagger_mutex;
	current_mutex warning_mutex;

	// FPGA Connection
	okCFrontPanel *xem;
	okCPLL22150 *pll;
	int chans[channels];
	bool fpga_connected;
	bool suppress_warnings;
	bool fpga_channel_reconfig;
	bool running;
	current_mutex channel_mutex;

	// Buffer Mutexes
	current_mutex read_mutex;
	current_mutex convert_mutex;
	
	// Convert Data
	long long rollover;
	long long distribution_counts[channels][distribution];
	long long distribution_ps[channels][distribution];

	// iterator
	_Iter* iterators;

	static bool autostart;
};

enum ITERATOR_STATES {
	STATE_STOPPED=0,
	STATE_IDLE=1,
	STATE_RUNNING=2
};

/** Base class for all ITerators
 */
class _Iterator {
public:
	/** standart constructor
	 * will register with the default TimeTagger
	 * note: running _must_ be set to one in child class
	 */
	_Iterator();
	virtual ~_Iterator();
	
	void _next(_Worker* w);
	
	virtual void start();
	virtual void stop();

	virtual const char *getClassName() = 0;
	virtual const char *getScpiCommand() = 0;
	virtual std::string getScpiParameters() = 0;
	virtual void getMeta(long long **ARGOUTVIEWM_ARRAY2, int *DIM1) = 0;

	int get_status();

	void set_start_trigger_channel(int);
	void set_end_trigger_channel(int);
	void set_start_trigger_time(long);
	void set_end_trigger_time(long);

	int get_start_trigger_channel();
	int get_end_trigger_channel();
	long get_start_trigger_time();
	long get_end_trigger_time();

protected:
	void registerChannel(int chan);
	void unregisterChannel(int chan);
	
	int lock();
	int unlock();

	virtual void next(Tag* list, int count, long long time) = 0;
	virtual void clear() = 0;
	

private:
	_Tagger* tagger;
	bool chans[channels];
	_Iter* iter;

	int state;

	int trigger_start_channel;
	int trigger_end_channel;
	int trigger_start_time;
	int trigger_end_time;
	
	current_mutex read_mutex;
};

class _Worker {
public:
	_Worker(_Tagger* t);
	~_Worker();
	
	void terminate() {
		run = false;
	}

	// Buffer between FPGA-Fifo and PC
	unsigned char ibuffer[ibuffersize];
	int ifull_usage;

	// Buffer after convertion
	Tag obuffer[obuffersize];
	int obuffer_usage;
	long long time;

private:
	void work();
	static void start(_Worker *w);
	
	// worker thread
	bool run;
	_Tagger* tagger;
	current_thread thread;
	
};

class _Iter {
public:
	_Iter() { next=0; iter=0; };
	
	_Iter* next;
	_Iterator* iter;
	
	current_mutex* getMutex() { return &mutex; };
	
private:
	current_mutex mutex;
};


#endif
